#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <linux/fb.h>
#include<stdlib.h>
struct fb_var_screeninfo var;  //用来接收屏幕信息
static unsigned int line_width;
static unsigned int pixel_width;
static int screen_size;
static unsigned char*mmap_addr;

void Show_Points(int x,int y,unsigned int color)
{
    //求出当前点的地址
    unsigned char *pen_8 = mmap_addr+y*line_width+x*pixel_width;

    //还得强转出16位和32位的指针
    unsigned short *pen_16;	
	unsigned int *pen_32;
    unsigned int red, green, blue;

    pen_16= (unsigned short *)pen_8;
	pen_32 = (unsigned int *)pen_8;


    //根据不同bpp分类
    switch(var.bits_per_pixel)
    {
        case 8:
        {
            *pen_8=color;
        }break;
        case 16:
        {
            //RGB 565
            //原本RGB是0X00RRGGBB 即RGB888  最高的那八位是无效的
            //取出RR  保留高位是为了尽可能保留颜色的精度。低位通常包含较少的颜色信息
            red   = (color >> 16) & 0xff; //0x000000RR  高5
            green = (color >> 8) & 0xff;  //0x000000GG  高6
			blue  = (color >> 0) & 0xff;  //0x000000BB  高5
            color=((red>>3)<<11)|((green>>2)<<5)|(blue>>3);
            *pen_16 = color;

        }break;
        case 32:
        {
            *pen_32=color;
        }break;

        default:
        {
            printf("can't surport %dbpp\n", var.bits_per_pixel);
        }break;
    }
}



int main(int argc, char*argv[])
{
    //文件/dev/fb0就是控制屏幕上的每一点的颜色的文件

    int i=0;
    int fd=open("/dev/fb0",O_RDWR); 
    if(fd<0)
    {
        printf("open fb0 error\n");
        exit(-1);
    }

    //int ioctl(int fd, unsigned long request, ...);
    //...可选参数，一般是指针，用来传递数据
    //它允许用户空间程序与内核空间的设备驱动程序进行交互，
    //执行特定的控制操作
    if(ioctl(fd,FBIOGET_VSCREENINFO,&var))
    {
        printf("can't get var\n");
        exit(-2);
    }

    //映射内存
    //void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);
    //addr:映射的起始地址，我们从NULL开始映射
    //length:映射的大小
    //prot：PROT_READ | PROT_WRITE：映射区域的保护标志，表示该区域可读可写。
    //flag:与其他进程的交互
    //offset:文件偏移量，表示从文件的开始位置进行映射。
    //On error,  the  value  MAP_FAILED(that is, (void *) -1) is returned
    //成功：返回映射的起始地址
    
    //x分辨率*每个像素的位数/8=X一行的字节
    line_width= var.xres * var.bits_per_pixel / 8;
    //计算一个像素的字节宽度
    pixel_width = var.bits_per_pixel / 8;

    //大小
    screen_size= var.xres * var.yres * var.bits_per_pixel / 8;

    mmap_addr=(unsigned char*)mmap(NULL,screen_size,PROT_READ | PROT_WRITE,MAP_SHARED,fd,0);
    if(mmap_addr==(unsigned char*)-1)
    {
        printf("can't mmap\n");
        exit(-3);
    }


    //开始操作屏幕
    //这里是 0xff，表示每个字节都设置为 11111111（二进制），即白色。
    memset(mmap_addr,0xff,screen_size);


    //实现画一条线（先实现画一个点）
    for(i=0;i<100;i++)
    {
        //0xFF0000:红色
        Show_Points(var.xres/2+i, var.yres/2, 0xFF0000);
    }

    //解除内存映射，释放内存区域
    munmap(mmap_addr,screen_size);
    //关闭文件
    close(fd);

    return 0;
}